package com.jielihaofeng.chat.localcache;

import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.context.SmartLifecycle;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Description: 分布式本地缓存，后续实现分布式刷新
 *
 * @author xieli
 * @version 1.0
 * @date 2022/10/3 4:51 PM
 * @since JDK 1.8
 */
@Slf4j
public abstract class AbstractTimedRefreshLocalCache<K, V> extends AbstractLocalCache<K, V> implements SmartLifecycle {

    protected AsyncLoadingCache<K, V> localCache;

    public AbstractTimedRefreshLocalCache() {
        this.localCache = Caffeine.newBuilder()
                .maximumSize(maximumSize())
                .refreshAfterWrite(refreshPeriodMinutes() + 1, TimeUnit.MINUTES)
                .expireAfterWrite(refreshPeriodMinutes() + 2, TimeUnit.MINUTES)
                .buildAsync(new CacheLoader<K, V>() {
                    @Override
                    public @Nullable V load(@NonNull K k) throws Exception {
                        return loadFromDB(k);
                    }

                    @Override
                    public @Nullable V reload(@NonNull K key, @NonNull V oldValue) throws Exception {
                        return load(key);
                    }

                    @Override
                    public @NonNull Map<@NonNull K, @NonNull V> loadAll(@NonNull Iterable<? extends @NonNull K> keys) throws Exception {
                        return loadAll(keys);
                    }
                });
    }

    @Override
    public V get(K k) {
        try {
            return this.localCache.synchronous().get(k);
        } catch (Exception e) {
            log.error("[{}] get error. cacheType:{}, key:{}.", getClass().getSimpleName(), cacheType(), k, e);
            return null;
        }
    }

    @Override
    public List<V> getAll(List<K> keys) {
        try {
            return new ArrayList<>(this.localCache.synchronous().getAll(keys).values());
        } catch (Exception e) {
            log.error("[{}] getAll error. cacheType:{}, keys:{}.", getClass().getSimpleName(), cacheType(), Arrays.toString(keys.toArray()));
            return new ArrayList<>();
        }
    }

    @Override
    public List<V> getAll() {
        try {
            return new ArrayList<>(this.localCache.synchronous().asMap().values());
        } catch (Exception e) {
            log.error("[{}] getAll error. cacheType:{}.", getClass().getSimpleName(), cacheType());
            return new ArrayList<>();
        }
    }

    @Override
    public void refreshAll(CacheRefreshEvent event) {
        // TODO 后续搞
    }

    @Override
    public void publishRefreshEvent(boolean auto) {
        // TODO 后续搞
    }

    @Override
    public boolean isAutoStartup() {
        return true;
    }

    @Override
    public boolean isRunning() {
        return this.isRunning;

    }

    @Override
    public void start() {
        this.isRunning = true;
        long start = System.currentTimeMillis();
        if (isLoadAll()) {
            log.info("LocalCache {} init start...", this.cacheType());
            doRefresh();
            log.info("LocalCache {} init end, take time: {}.", this.cacheType(), System.currentTimeMillis() - start);
        }
    }

    @Override
    public void stop() {
        this.isRunning = false;
    }

    @Override
    public void stop(Runnable callback) {
    }

    @Override
    public int getPhase() {
        return Integer.MAX_VALUE;
    }

    /**
     * 查询数据库
     * @param k
     * @return
     */
    protected abstract V loadFromDB(K k);

    /**
     * 批量查询数据库
     * @param keys
     * @return
     */
    protected abstract Map<K, V> loadAllFromDB(List<K> keys);

    /**
     * 查询全部
     * @return
     */
    protected abstract Map<K, V> loadAll();

    /**
     * 是否需要初始化
     * @return
     */
    protected abstract boolean isLoadAll();

    /**
     * 缓存容量
     * @return
     */
   protected abstract long maximumSize();

    /**
     * 过期时间
     * @return
     */
    protected abstract long refreshPeriodMinutes();

    private void doRefresh() {
        if (!this.isLoadAll()) {
            this.localCache.synchronous().invalidateAll();
        } else {
            Map<K, V> values = this.loadAll();
            this.localCache.synchronous().invalidateAll();
            this.localCache.synchronous().putAll(values);
        }
    }

}
